# _*_ coding : utf-8 _*_
# @Time: 2024/9/19 14:03
# @Author : 石文帅	
# @File : basice.py
# @Project : python_slfh
import cv2
import numpy
from selenium.webdriver import Keys
from PIL import ImageChops, Image
from common.getini import get_db_data
from common.log import log_site
from selenium.webdriver.common.action_chains import ActionChains
from xpathDictionary.slfhXpath import *
from selenium.webdriver.common.by import By
import time
import pyperclip
import pyautogui
import urllib.request
import configparser
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


log = log_site()
driver_path = get_db_data('pageboys', 'chrome')
url_path = get_db_data('pageboys', 'address')
path = get_db_data('pageboys', 'previous_path')
path_img = f'{path}/img/'
path_file = f'{path}/file/'
path_expected_img = f'{path}/img/expected_img/'


def element_exist(self, locator, num=3):
    """
    判断元素是否存在 ,存在返回True 不存在返回False
    :param self:
    :param locator: XPATH元素定位
    :param num: 默认是5s的等待
    :return: 返回True为能查到元素，返回False为没有找到元素
    """
    self.implicitly_wait(num)
    try:
        element = self.find_element(By.XPATH, locator)
        if element is not None:
            log.info(f'找到元素{locator}--返回True')
            return True
    except Exception as e:
        log.info(f'没有找到元素{locator}--返回False')
    finally:
        time.sleep(0.5)
    return False


def element_exist_gettext(self, locator, num=3):
    """
    判断元素是否存在 ,存在返回text 不存在返回False
    :param self:
    :param locator: XPATH元素定位
    :param num: 默认是5s的等待
    :return: 返回True为能查到元素，返回False为没有找到元素
    """
    self.implicitly_wait(num)
    try:
        element = self.find_element(By.XPATH, locator)
        if element is not None:
            log.info(f'找到元素{locator}--返回True')
            return element.text
    except Exception as e:
        log.info(f'没有找到元素{locator}--返回False')
    finally:
        time.sleep(0.5)
    return False


def element_exist_click(self, locator, num=5):
    """
    判断元素是否存在 ,存在就点击，不存在就跳过 不报错
    :param self:
    :param locator: XPATH元素定位
    :param num: 默认是5s的等待
    :return: 返回True为能查到元素，返回False为没有找到元素
    """
    self.implicitly_wait(num)
    try:
        element = self.find_element(By.XPATH, locator)
        if element is not None:
            log.info(f'找到元素{locator}--执行点击')
            element.click()
            return True
    except Exception as e:
        log.info(f'没有找到元素{locator}--跳过点击')
    finally:
        time.sleep(0.5)
    return False


def element_exists(self, locator, num=5):
    """
    判断元素是否存在 ,存在返回True 不存在返回False
    :param self:
    :param locator: XPATH元素定位
    :param num: 默认是5s的等待
    :return: 返回True为能查到元素，返回False为没有找到元素
    """
    self.implicitly_wait(num)
    try:
        element = self.find_elements(By.XPATH, locator)
        if element is not None:
            log.info(f'找到元素{locator}--返回True')
            return True
    except Exception as e:
        log.info(f'没有找到元素{locator}--返回False')
    finally:
        time.sleep(1)
    return False


def locate(self, locator):
    """
    元素定位XPATH，返回单个元素
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    self.implicitly_wait(10)
    try:
        element = self.find_element(By.XPATH, locator)
        if element is not None:
            return element
    except Exception as e:
        log.info(f'没有找到元素{locator},{e}')
        raise


def locate_text(self, locator):
    """
    元素定位XPATH，返回单个元素的text
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    return locate(self, locator).text


def locate_texts(self, locator):
    """
    元素定位XPATH，以list返回元素的text
    :param self:
    :param locator: XPATH元素定位
    :return:list
    """
    try:
        elements = self.find_elements(By.XPATH, locator)
        if elements is not None:
            target = []
            for element in elements:
                target.append(element.text)
            return target
    except Exception as e:
        log.info(f'没有找到元素{locator}')
        raise
    finally:
        time.sleep(1)


def locates(self, locator):
    """
    元素定位XPATH 返回多个元素
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    self.implicitly_wait(10)
    try:
        elements = self.find_elements(By.XPATH, locator)
        if elements is not None:
            return elements
    except Exception as e:
        log.info(f'没有找到元素{locator}')
        raise
    finally:
        time.sleep(1)


def locates_for_list(self, locator):
    """
    元素定位返回list值
    :param self:
    :param locator:
    :return:List
    """
    elements = locates(self, locator)
    actual_list = []
    for element in elements:
        actual_list.append(element.text)
    return actual_list


def locates_last(self, locator):
    """
    多元素定位的最后一个元素
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    try:
        elements = self.find_elements(By.XPATH, locator)
        if elements is not None:
            return elements[-1]
    except Exception as e:
        log.info(f'没有找到元素{locator}')
        raise
    finally:
        time.sleep(1)


def locates_last_click(self, locator):
    """
    点击多元素定位的最后一个元素
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    try:
        elements = self.find_elements(By.XPATH, locator)
        if elements is not None:
            elements[-1].click()
    except Exception as e:
        log.info(f'没有找到元素{locator}')
        raise
    finally:
        time.sleep(1)


def locates_last_input(self, locator, value):
    """
    输入多元素定位的最后一个元素
    :param value:
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    try:
        elements = self.find_elements(By.XPATH, locator)
        if elements is not None:
            elements[-1].click()
            elements[-1].clear()
            elements[-1].send_keys(value)
    except Exception as e:
        log.info(f'没有找到元素{locator}')
        raise
    finally:
        time.sleep(1)


def locate_click(self, locator):
    """
    点击元素
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    locate(self, locator).click()
    time.sleep(0.5)


def locates_click(self, locator, num=0):
    """
    点击元素
    :param num: 点击第几个从0开始默认不传num就是第一个
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    locates(self, locator)[num].click()
    time.sleep(0.5)


def locate_input(self, locator, text):
    """
    清空输入框并且输入text
    :param self:
    :param locator: XPATH元素定位
    :param text: 要输入的内容
    :return:
    """
    element = locate(self, locator)
    locate_clear(self, locator)
    element.send_keys(text)
    time.sleep(0.5)


def element_input(self, element, text):
    """
    清空输入框并且输入text
    :param self:
    :param element: 元素
    :param text: 要输入的内容
    :return:
    """
    locate_clear(self, element)
    element.send_keys(text)
    time.sleep(0.5)


def locates_input(self, list_target, locator):
    """
    以list的长度来匹配多个元素定位 并且input进去值
    :param self:
    :param list_target: 要输入的list值
    :param locator:目标元素定位
    :return:
    """
    elements = locates(self, locator)
    i = 0
    for target in list_target:
        if target is None:
            i = i + 1
            continue
        element_clear(elements[i])
        elements[i].send_keys(target)
        time.sleep(0.5)
        i = i+1


def locate_clear(self, locator):
    """
    清空输入框,妈的用clear()清空不了，我得换一种
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    element = locate(self, locator)
    # ctrl + a 全选
    element.send_keys(Keys.CONTROL, 'a')
    # 删除内容delete
    element.send_keys(Keys.DELETE)


def element_clear(element):
    """
    直接传元素清空输入框，自己提前定位，一般用于一次定位出多个元素
    :param element: 元素
    :return:
    """
    # ctrl + a 全选
    element.send_keys(Keys.CONTROL, 'a')
    # 删除内容delete
    element.send_keys(Keys.DELETE)


def locate_slfh_clear(self, locator):
    """
    在森林防火里面很多搜索框 直接用clear()方法无法彻底删除输入框种的内容
    所以需要判断一下是否有值 无值跳过，有值点击输入框右边的叉叉符号
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    # 鼠标悬停上去判断元素定位的兄弟节点+span/span/li（小叉叉）是否存在
    mouse_hover(self, locator)
    time.sleep(0.5)
    element = locator + '/../span/span/i'
    if element_exist(self, element, 1):
        locate_click(self, element)


def locate_slfh2_clear(self, locator):
    """
    在森林防火里面很多搜索框 直接用clear()方法无法彻底删除输入框种的内容
    所以需要判断一下是否有值 无值跳过，有值点击输入框右边的叉叉符号
    :param self:
    :param locator: XPATH元素定位
    :return:
    """
    # 鼠标悬停上去判断元素定位的兄弟节点+span/span/li（小叉叉）是否存在
    mouse_hover(self, locator)
    time.sleep(0.5)
    element = locator + '/../span/span/i[2]'
    if element_exist(self, element, 1):
        locate_click(self, element)


def assert_equal(actual, expected, message='不一致'):
    """
    封装断言
    :param actual: 实际值
    :param expected: 预计值
    :param message: 不等的时候显示信息
    :return:
    """
    if actual != expected:
        log.info(f"实际值: {actual}, 预期值: {expected},{message}")
        raise
    else:
        log.info(f"实际值: {actual}==预期值: {expected}")


def assert_in(str1, str2):
    """
    str1  包含在  str2 中
    :param str1: 短的字符串
    :param str2: 长的字符串
    :return:
    """
    if str1 in str2:
        log.info(f"str1: {str1}, 包含在: {str2}中")
    else:
        log.info(f"str1: {str1}, 不包含在: {str2}中")
        raise


def assert_str_element(self, expected, locator, message='不一致'):
    """
    封装断言
    :param self:
    :param expected: 预计值
    :param locator: 元素定位
    :param message: 不等的时候显示信息
    :return:
    """
    actual = locate_text(self, locator)
    if actual != expected:
        log.info(f"实际值: {actual}, 预期值: {expected},{message}")
        raise
    else:
        log.info(f"实际值: {actual}==预期值: {expected}")


def assert_true(actual, message='不一致'):
    """
    判断为True
    :param actual:
    :param message:
    :return:
    """
    if not actual:
        log.info(f"实际值: {actual}, 预期值: True,{message}")
        raise
    else:
        log.info(f"实际值: {actual}==预期值: True")


def assert_false(actual, message='不一致'):
    """
    判断为False
    :param actual:
    :param message:
    :return:
    """
    if actual:
        log.info(f"实际值: {actual}, 预期值: False,{message}")
        raise
    else:
        log.info(f"实际值: {actual}==预期值: False")


def assert_list(actualise, expected):
    """
    2个list做对比,判断个数为第一个list的长度,所以actual的长度要小于expected
    :param actualise: 实际值
    :param expected: 预计值
    :return:
    """
    i = 0
    for actual in actualise:
        assert_equal(actual, expected[i])
        i = i + 1


def assert_list_elements(self, locator, actualise):
    """
    2个list有序对比 以actualise的长度为准
    :param self:
    :param locator:元素定位
    :param actualise:对比的list值
    :return:
    """
    i = 0
    elements = locates(self, locator)
    for actual in actualise:
        assert_equal(actual, elements[i].text)
        i = i + 1


def assert_str_elements(self, str_value, locator):
    """
    list里面都是str值
    :param self:
    :param str_value:对比的str值
    :param locator:元素定位
    :return:
    """
    elements = locates(self, locator)
    for element in elements:
        assert_equal(str_value, element.text)


def assert_str_in_elements(self, str_value, locator):
    """
    断言str包含在 每个list中（模糊搜索）
    :param self:
    :param str_value:
    :param locator:
    :return:
    """
    elements = locates(self, locator)
    for element in elements:
        assert_in(str_value, element.text)


def list_comparison(old_list, new_list):
    """
    2个list比较，判断新list少了哪些，多了哪些(list里无重复数据)
    :param self:
    :param old_list: 老的list
    :param new_list: 新的list
    :return:add_list(要新增的), del_list（要删除的）
    """

    add_list = [item for item in new_list if item not in old_list]
    del_list = [item for item in old_list if item not in new_list]

    return add_list, del_list


def mouse_hover(self, locator):
    """
    鼠标悬停到元素上
    :param self:
    :param locator: 元素定位
    :return:
    """
    hover_element = locate(self, locator)
    ActionChains(self).move_to_element(hover_element).perform()


def mouse_hover_click(self, locator):
    """
    鼠标悬停到元素上停留0.5s后点击
    :param self:
    :param locator: 元素定位
    :return:
    """
    mouse_hover(self, locator)
    time.sleep(0.5)
    locate_click(self, locator)


def save_img_locate(self, locator, img_name='temporary.png'):
    """
    保存图片，保存到img文件夹下，名字为临时temporary
    :param self:
    :param locator:元素定位
    :param img_name:图片名字，默认temporary.png
    :return:返回截图的图片地址
    """
    temporary_path = f'{path_img}{img_name}'
    element = locate(self, locator)
    screenshot = element.screenshot_as_png
    with open(temporary_path, 'wb') as file:
        file.write(screenshot)
    return temporary_path


def cv_imread(file_path):
    cv_img = cv2.imdecode(numpy.fromfile(file_path, dtype=numpy.uint8), -1)
    return cv_img


def compare_images(image1_path, image2_path, threshold=0.98):
    """
    图片对比
    :param image1_path: 第一张图片的路径地址
    :param image2_path: 第二张图片的路径地址
    :param threshold: 阈值 最多 多少不同
    :return: 相似度大于默认的0.98返回True，小于返回False
    """
    # 加载图像
    image1 = cv_imread(image1_path)
    image2 = cv_imread(image2_path)

    # 将图像转换为灰度图
    gray_image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)

    gray_image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

    # 计算直方图
    hist1 = cv2.calcHist([gray_image1], [0], None, [256], [0, 256])
    hist2 = cv2.calcHist([gray_image2], [0], None, [256], [0, 256])

    # 比较直方图
    similarity = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)

    log.info(f"图像相似度：{similarity}")
    if similarity - threshold >= 0:
        return True
    else:
        return False


def assert_img_locate(self, image_name, locator):
    """
    断言元素定位截取下来的图片和目标图片是否一致
    :param self:
    :param image_name: 图片名称,expected_img的和img文件夹下的名字会相同，img下的是当前实际的
    :param locator: 元素
    :return:
    """
    image1_path = f'{path_expected_img}{image_name}'
    image2_path = save_img_locate(self, locator, f'当前-{image_name}')
    assert_true(compare_images(image1_path, image2_path))


def assert_download_src_img_locate(self, image_name, locator):
    """
    下载图片再做对比，藏用于小图缩略图的对比
    :param self:
    :param image_name: 图片名称,expected_img的和img文件夹下的名字会相同，img下的是当前实际的
    :param locator: 元素
    :return:
    """
    image1_path = f'{path_expected_img}{image_name}'
    image2_path = f'{path_img}当前-{image_name}'
    element = locate(self, locator)
    image_url = element.get_attribute('src')
    urllib.request.urlretrieve(image_url,image2_path)
    assert_true(compare_images(image1_path, image2_path))


def locate_sendImg(self, img_path, locator):
    """
    上传图片
    :param self:
    :param img_path: 图片的路径
    :param locator: 元素定位
    :return:
    """
    element = locate(self, locator)
    element.send_keys(img_path)
    # 图片提交后上传有点慢，需要等待一会，这里后期优化
    time.sleep(3)


def move_find_element(self, scrollable_locator, target_locator, num=500):
    """
    向下滑动到找到元素可见、可点击为止，，，，，，，有问题，暂时没用到，以后调试
    :param self:
    :param scrollable_locator: 滚动的元素框框
    :param target_locator: 想要找到的元素
    :param num: 滑动几次 默认5次
    :return:
    """
    # 将滚动动作的作用对象设置为可滚动的元素
    scrollable_element = locate(self, scrollable_locator)
    # 使用ActionChains创建一个滚动动作
    action = ActionChains(self)
    # 将滚动动作的作用对象设置为可滚动的元素
    action.move_to_element(scrollable_element)
    # 执行滚动动作
    action.perform()

    while True:
        # 尝试找到目标元素
        target_element = locate(self, target_locator)
        if target_element.is_enabled():
            # 如果找到并且元素是可见的，则停止滚动
            break
        else:
            # 如果没找到或元素不可见，继续滚动
            # 这里可以调整滚动的距离和次数
            for i in range(num):  # 滚动5次
                self.execute_script("arguments[0].scrollTop += arguments[1];", scrollable_element, 1000)
                self.implicitly_wait(0.5)  # 等待一段时间，避免过度滚动


def get_input_value(self, locator):
    """
    返回输入框里面的值
    :param self: self.wd
    :param locator: 定位
    :return: str
    """
    # return locate(self, locator).get_attribute('value')
    # element.get_property('attributes') 输出所有元素属性
    element = locate(self, locator)
    text = element.get_attribute('value')
    return text


def get_textarea_value(self, locator):
    """
    返回富文本框里面的值
    :param self: self.wd
    :param locator: 定位
    :return: str
    """
    # return locate(self, locator).get_attribute('value')
    element = locate(self, locator)
    text = element.get_attribute('value')
    return text


def assert_input_value(self, expected, locator):
    """
    断言输入框input里面的值，常用于断言编辑的时候数据是否回填正确
    :param self:
    :param expected: 期望值
    :param locator: 元素定位
    :return:
    """
    actual_value = get_input_value(self, locator)
    assert_equal(actual_value, expected)


def upload_file_by_pyauto(self, locator, file):
    """
    文件上传 非input按钮，使用pyautogui
    :param self:
    :param locator: 上传按钮元素定位
    :param file: 文件路径用绝对路径吧
    :return:
    """
    # 需要替换文件路径里面的'/'
    file = file.replace('/', '\\')
    try:
        locate_click(self, locator)
        time.sleep(2) # 多等一会，不然很可能窗口没打开就开始 ctrl+v了
        pyperclip.copy(file)
        pyautogui.hotkey('ctrl', 'v')
        pyautogui.press(keys='enter', presses=1)  # 点击确定，一次
        time.sleep(1)
    except Exception as e:
        raise log.error(f'文件上传失败:{e}')


def upload_file_by_input(self, locator, file):
    """
    文件上传 是input按钮，直接使用send
    :param self:
    :param locator: 上传按钮元素定位
    :param file: 文件路径用绝对路径吧
    :return:
    """
    # 需要替换文件路径里面的'/'
    file = file.replace('/', '\\')
    try:
        element = locate(self, locator)
        element.send_keys(file)
    except Exception as e:
        raise log.error(f'文件上传失败:{e}')